﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System.Diagnostics;


namespace intelliEssay.Core
{
    public class TaskProgressed : EventArgs
    {
        public TaskState CurrentState;
        public int ReportedPercentage;

    }

    public enum TaskRunMode
    {
        Shell,
        Console
    }
    /// <summary>
    /// This class represents a Task
    /// </summary>
    /// <include file='DocuemntFormat.dll' path='[@name="Unknown"]'/>
    public class Task : IDisposable
    {
        /// <summary>
        /// Fires when a checker starts its work.
        /// </summary>
        public event EventHandler<TaskProgressed> OnTaskProgress;
        private bool Disposed = false;
        private EssayExceptionCollection essayExeptionCollection = new EssayExceptionCollection();
        /// <summary>
        /// The current state of the task
        /// <see cref="TaskState"/>
        /// </summary>
        public TaskState CurrentState { get; set; }
        /// <summary>
        /// The path of the file to be checked. This path should be in a format of "x:\xxx\xxx.xxx"
        /// </summary>
        public string FilePath { get; set; }
        private List<IEssayChecker> Checkers = new List<IEssayChecker>();
        /// <summary>
        /// Represents the WordProcessingDocument.MainDocument part of the task.
        /// </summary>
        public WordprocessingDocument TaskObjectRoot { get; set; }
        /// <summary>
        /// for intelliEssay.Core.MainAssembly.dll use only. 
        /// </summary>
        internal string relationshipID = "";
        /// <summary>
        /// Type of the task. must set.
        /// <para>This is a case-sensitive type used to determine which checkers to run</para>
        /// </summary>
        /// <remarks>
        /// Currently, taskType should be "nkuB", indicating NanKai University
        /// </remarks>
        public string taskType { get; set; }
        private object concurrentLock = new object();
        /// <summary>
        /// The task setting that is associated with the task
        /// </summary>
        public TaskSettings taskSetting = new TaskSettings();
        private DocumentStructure.DocumentStructure _documentStructure;
        /// <summary>
        /// Gets the documentStructure of this task
        /// </summary>
        public DocumentStructure.DocumentStructure documentStructure
        {
            get
            {
                if (_documentStructure == null)
                {
                    _documentStructure = DocumentStructure.DocumentStructure.GenerateDocumentStructure(WordprocessingDocument.Open(FilePath, false));
                }
                return _documentStructure;
            }
            set
            {
                _documentStructure = value;
            }
        }
        /// <summary>
        /// Instantiates task instance. Note this constructor do nothing.
        /// </summary>
        /// 
        string userFriendlyIdentifier;

        public string UserFriendlyIndentifer
        {
            get
            {
                return userFriendlyIdentifier;
            }
        }
        public Task()
        {
            CurrentState = TaskState.Uninitialized;
        }

        private void FireTaskStatusChange(int reportedPercent)
        {
            if (OnTaskProgress != null)
            {
                OnTaskProgress(this, new TaskProgressed() { CurrentState = CurrentState, ReportedPercentage = reportedPercent });
            }
        }

        /// <summary>
        /// The number of checkers.
        /// </summary>
        public int CheckerCount
        {
            get
            {
                return Checkers.Count;
            }
        }

        /// <summary>
        /// Run task
        /// </summary>
        public void Run(TaskRunMode runMode = TaskRunMode.Shell)
        {
            TimingClass tc = new TimingClass();
            DebugDisplaySystem dds = new DebugDisplaySystem();
            this.CurrentState = TaskState.Running;
            TaskRuntimeException taskRuntimeException = new TaskRuntimeException() { RuntimeExceptions = new List<Exception>() };
            foreach (IEssayChecker iec in Checkers)
            {
                //try
                //{
                    FireTaskStatusChange(20 + (int)Math.Ceiling(60 * (double)(Checkers.IndexOf(iec) + 1) / (double)Checkers.Count));
                    if (runMode == TaskRunMode.Console)
                    {
                        Console.WriteLine("Performance Information");
                        dds.Title = iec.GetType().Name;
                        tc.StartTimer();
                    }
                    iec.Run(this);
                    if (runMode == TaskRunMode.Console)
                    {
                        tc.StopTimer();
                        dds.Content = "Pure CPU Time(Thread 0):" + tc.Duration + "         GCTimesG0/G1/G2:" + GC.CollectionCount(0).ToString() + "/" + GC.CollectionCount(1).ToString() + "/" + GC.CollectionCount(2).ToString();
                        dds.TidyPrint(2);
                    }
                //}
                //catch (Exception ex)
                //{
                //    this.CurrentState = TaskState.RunningWithErrors;
                //    taskRuntimeException.RuntimeExceptions.Add(ex);
                //    if (runMode == TaskRunMode.Shell)
                //        continue;
                //    else throw ex;
                //}
            }

            Console.WriteLine("Press Any Key To Show Exceptions".PadLeft(24, '*').PadRight(24, '*'));
            if (runMode == TaskRunMode.Console)
                Console.ReadKey();
            int idx = 0;
            var essayXception = essayExeptionCollection.Merge();
            this.CurrentState = TaskState.MergingFoundErrors;
            FireTaskStatusChange(80);
            if (essayXception != null)
            {
                foreach (ChainedException ee in essayXception)
                {
                    if (runMode == TaskRunMode.Console)
                    {
                        Console.WriteLine(++idx);
                    }
                    if (idx % 30 == 0)
                    {
                        if (runMode == TaskRunMode.Console)
                        {
                            Console.WriteLine("Press Any Key To Show More Exceptions".PadLeft(24, '*').PadRight(24, '*'));
                            Console.ReadKey();
                        }
                    }
                    if (runMode == TaskRunMode.Console)
                    {
                        dds = new DebugDisplaySystem();
                        dds.Title = "Exception";
                        dds.Content = ee.ToString();
                        dds.TidyPrint(2);
                    }
                }
            }
            if (runMode == TaskRunMode.Console)
            {
                Console.WriteLine("Press Any Key To Exit".PadLeft(24, '*').PadRight(24, '*'));
                Console.ReadKey();
            }
            FireTaskStatusChange(100);
            if (taskRuntimeException.RuntimeExceptions.Count != 0)
            {
                this.CurrentState = TaskState.FinishedWithErrors;
                throw taskRuntimeException;
            }
            else
            {
                this.CurrentState = TaskState.Finished;
            }
        }
        /// <summary>
        /// This method is used to initialize the task.
        /// <remarks>
        /// This method MUST BE CALLED before Run() or DebugRun() is called, or the system fails
        /// </remarks>
        /// </summary>
        public void Initialize()
        {
            Monitor.Enter(concurrentLock);
            TaskPool.TasksInPool.Add(this);
            System.IO.FileInfo fi = new System.IO.FileInfo(FilePath);
            userFriendlyIdentifier = fi.Name;
            CurrentState = TaskState.Initializing;
            FireTaskStatusChange(0);
            switch (taskType)
            {
                default:
                    {
                        TaskObjectRoot = WordprocessingDocument.Open(FilePath, false);
                        documentStructure = DocumentStructure.DocumentStructure.GenerateDocumentStructure(TaskObjectRoot);
                        documentStructure.documentElementSequence = DocumentStructure.DocumentKeyElementSequence.GenerateDocumentElementSequence(this);
                        CurrentState = TaskState.Ready;
                        FireTaskStatusChange(20);
                        break;
                    }
            }
            Monitor.Exit(concurrentLock);
        }

        [Obsolete]
        public void AddMultipleExceptionsToEssayExceptionCollection(EssayExceptionCollection eec)
        {
            System.Threading.Monitor.Enter(essayExeptionCollection);
            essayExeptionCollection.AddRange(eec);
            System.Threading.Monitor.Exit(essayExeptionCollection);
        }
        /// <summary>
        /// Add an essay exception to the current task's essay exception collection in a thread-safe way.
        /// </summary>
        /// <param name="iee">The exception to add to the collection</param>
        public void AddEssayExceptionToEssayExceptionCollection(EssayException ee)
        {
            System.Threading.Monitor.Enter(essayExeptionCollection);
            essayExeptionCollection.Add(ee);
            System.Threading.Monitor.Exit(essayExeptionCollection);
        }
        /// <summary>
        /// Retrieves the index of element of EssayExceptionCollection in a thread-safe way
        /// </summary>
        /// <param name="index">the index, starting from 0</param>
        /// <returns></returns>
        public EssayException RetrieveEssayExceptionFromEssayExceptionCollection(int index)
        {
            EssayException result;
            System.Threading.Monitor.Enter(essayExeptionCollection);
            result = essayExeptionCollection[index];
            System.Threading.Monitor.Exit(essayExeptionCollection);
            return result;
        }
        /// <summary>
        /// Adds the essay checker to the task, and finally invoked by the Run() and DebugRun() - This is not thread safe
        /// </summary>
        /// <param name="essayChecker">the checker to be added.</param>
        public void AddAChecker(IEssayChecker essayChecker)
        {
            Checkers.Add(essayChecker);
        }
        /// <summary>
        /// Destroy this task, and set it to Abort state
        /// <remarks>
        /// This method essentially cancel the current-running checker, and removes all checkers that are not started.
        /// </remarks>
        /// </summary>
        public void Kill()
        {
            Monitor.Enter(concurrentLock);
            CurrentState = TaskState.Aborted;
            Monitor.Exit(concurrentLock);
        }
        /// <summary>
        /// Cleans up the resources used by this task.
        /// <para>Calling this method after calling Task.TaskObjectRoot.Dispose() would cause an exception</para>
        /// </summary>
        public void Dispose()
        {
            Dispose(false);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool FinalizerCalling)
        {
            if (!FinalizerCalling)
            {
                if (!Disposed)
                {
                    Disposed = true;
                    this.CurrentState = TaskState.Disposing;
                    TaskObjectRoot.Dispose();
                    this.CurrentState = TaskState.Disposed;
                }
            }
        }
        /// <summary>
        /// This function will return a Member-Wise Copy of EssayException.
        /// </summary>
        /// <returns></returns>
        public List<EssayException> GetEssayExceptionSnapShot()
        {
            List<EssayException> snapShot = new List<EssayException>();
            Monitor.Enter(essayExeptionCollection);
            essayExeptionCollection.ForEach(o =>
                {
                    snapShot.Add(o);
                });
            Monitor.Exit(essayExeptionCollection);
            return snapShot;
        }
        /// <summary>
        /// gets the collection of the exception
        /// </summary>
        /// <returns></returns>
        public EssayExceptionCollection GetEssayExceptionCollectionSnapShot()
        {
            return essayExeptionCollection;
        }
        /// <summary>
        /// 优雅地关闭这个任务
        /// </summary>
        ~Task()
        {
            Dispose(true);
        }
    }
    /// <summary>
    /// 描述任务当前状态
    /// </summary>
    public enum TaskState
    {
        /// <summary>
        /// 未初始化状态。未指定任务路径之前的任务状态。
        /// </summary>
        Uninitialized,
        /// <summary>
        /// 就绪状态。就绪状态指用户指定任务之后，任务开始执行之前的状态。
        /// </summary>
        Ready,
        /// <summary>
        /// 运行状态。运行状态指用户指定任务开始执行，但是尚未进入终止、放弃、完成状态时的状态。
        /// </summary>
        Running,
        /// <summary>
        /// 放弃状态。当用户在就绪状态或者运行状态按下取消按钮时，任务进入放弃状态。
        /// </summary>
        Aborted,
        /// <summary>
        /// 终止状态。当指定任务进入运行状态后，在运行过程中发生重大错误使得运行不可继续时（如空白文档）时任务的状态。当程序设计本身出问题时，任务不进入这个状态。
        /// </summary>
        Terminated,
        /// <summary>
        /// 任务历经就绪态、运行态顺利完成后进入的状态。
        /// </summary>
        Finished,
        /// <summary>
        /// 在任务运行的过程中，程序本身出现异常时任务进入的状态。当任务尚未进入运行状态时，不得进入这个状态。
        /// </summary>
        BugCheck,
        /// <summary>
        /// The task is being initialized
        /// </summary>
        Initializing,
        /// <summary>
        /// The task's associated exceptions are being merged.
        /// </summary>
        MergingFoundErrors,
        FinishedWithErrors,
        RunningWithErrors,
        Disposing,
        Disposed
    }
    class DebugDisplaySystem
    {
        public string Title;
        public string Content;
        public void TidyPrint(int mandatorySpacingBeforeAndAfter, bool forceConsoleResize = true)
        {
            Console.WriteLine();
            if (forceConsoleResize)
            {
                Console.WindowWidth = Console.LargestWindowWidth * 9 / 10;
                Console.WindowHeight = Console.LargestWindowHeight * 9 / 10;
            }
            var windowWidth = Console.WindowWidth;
            if (mandatorySpacingBeforeAndAfter * 2 - windowWidth >= 0)
            {
                throw new InvalidOperationException();
            }
            int lines = Title.Length / (windowWidth - mandatorySpacingBeforeAndAfter * 2);
            int charactersPerLines = windowWidth - mandatorySpacingBeforeAndAfter * 2;
            string mandatoryPaddingOneSide = "";
            string asteriskDecoratorOneLine = "";
            for (int i = 0; i < mandatorySpacingBeforeAndAfter; i++)
            {
                mandatoryPaddingOneSide += " ";
            }
            for (int i = 0; i < windowWidth; i++)
            {
                asteriskDecoratorOneLine += "*";
            }
            Console.WriteLine(Title);

            lines = (int)Math.Ceiling((double)Content.Length / (double)(windowWidth - mandatorySpacingBeforeAndAfter * 2));
            charactersPerLines = windowWidth - mandatorySpacingBeforeAndAfter * 2;
            int asteriskEffectiveMaximumCharacterPerLine = charactersPerLines - 2;
            int remainingLength = Content.Length;
            int lastReadIdx = 0;

            Console.Write(asteriskDecoratorOneLine);
            string nextOutput;
            for (int i = 0; i < lines; i++)
            {
                if (remainingLength < asteriskEffectiveMaximumCharacterPerLine)
                {
                    nextOutput = Content.Substring(lastReadIdx, remainingLength).PadRight(asteriskEffectiveMaximumCharacterPerLine, ' ');
                    System.Diagnostics.Debug.Assert(lastReadIdx + remainingLength == Content.Length);
                    remainingLength = 0;
                }
                else
                {
                    nextOutput = Content.Substring(lastReadIdx, asteriskEffectiveMaximumCharacterPerLine);
                    remainingLength -= asteriskEffectiveMaximumCharacterPerLine;
                    lastReadIdx += asteriskEffectiveMaximumCharacterPerLine;
                }
                Console.Write("*" + mandatoryPaddingOneSide);
                Console.Write(nextOutput + mandatoryPaddingOneSide);
                Console.Write("*");
            }
            Console.Write(asteriskDecoratorOneLine);

        }
    }
    class TimingClass
    {
        TimeSpan StartTime;
        public TimeSpan Duration;
        public TimingClass()
        {
            StartTime = new TimeSpan(0);
            Duration = new TimeSpan(0);
        }
        public void StartTimer()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            StartTime = new TimeSpan();
            StartTime = Process.GetCurrentProcess().Threads[0].UserProcessorTime;

        }
        public TimeSpan ShowDuration()
        {
            Console.WriteLine(Duration);
            return Duration;
        }
        public void StopTimer()
        {
            Duration = Process.GetCurrentProcess().Threads[0].UserProcessorTime.Subtract(StartTime);
        }
    }
}

